Análise dos Acidentes de Trânsito de Belo Horizonte¶

Autores¶

  • Felipe Pereira
  • Isaac Reyes
  • João Pedro Martins

Sumário¶

  • Preparando os dados
    • Base de dados
    • Colando um índice de datas
    • Removendo as colunas que não precisamos
  • Questões
    • 2. Faça um gráfico de linhas por ano mês indicando o número de acidentes naquele ano mês
    • 3. Repita o gráfico acima por ano apenas
    • 4. Faça um gráfico de barras por ano indicandos os tipos de acidentes mais comuns no ano
    • 5. Repita o gráfico acima considerando gráficos fatais e não fatais
    • 6. Faça a mesma análise por bairro e por acidentes fatais e não fatais
    • 7. Plotar Mapa de Belo Horizonte por Tipo de Acidente
    • 8. Plotar Intervalo de Confiança via Bootstrap do Número de Acidentes por Mês
    • 9. Vamos brincar de regressão
    • 10. Use o Número de Acidentes por bairro de 2019 para Prever 2022
    • 11. Fazer Análises Adicionais de Interesse

Preparando os dados¶

Base de Dados¶

A base de dados a ser analisada consiste em registros de acidentes de trânsito na cidade de Belo Horizonte. A base é separada em vários arquivos csv que podem ser lidos diretamente com babypandas. Para isso, use o código abaixo:

In [1]:
import babypandas as bpd
import pandas as pd
import glob
import matplotlib.pyplot as plt
import numpy as np

df = bpd.DataFrame()
for f in glob.glob('dados/*.csv'):
    aux = bpd.read_csv(f, sep=';')
    df = df.append(aux)

Colando um índice de datas¶

Infelizmente, o BabyPandas não lida muito bem com datas. Por isso, vamos tratar as datas usando pandas. A função pd.to_datetime converte texto em datas, funcionando bem no nosso caso.

In [2]:
data_correta = pd.to_datetime(
    df.get('data hora_boletim').values
)

Agora basta colocar a data na coluna correta

In [3]:
df = df.assign(
    data_boletim = data_correta
)

Removendo as colunas que não precisamos¶

Por fim, vamos remover todas as colunas desnecessárias e configurar um índice.

In [4]:
df = df.drop(
    columns=['data hora_boletim',
             'data_inclusao',
             'valor_ups',
             'valor_ups_antiga',
             'data_alteracao_smsa',
             'descricao_ups_antiga']
).sort_values(by='data_boletim').set_index('data_boletim')
df
Out[4]:
numero_boletim tipo_acidente desc_tipo_acidente cod_tempo desc_tempo cod_pavimento pavimento cod_regional desc_regional origem_boletim ... coordenada_x coordenada_y hora_informada indicador_fatalidade descricao_ups ano mes lat lon bairro
data_boletim
2016-01-01 00:30:00 2016-008685542-001 H04000 QUEDA DE PESSOA DE VEICULO ... 0 NAO INFORMADO 0 NAO INFORMADO 21 NORDESTE POLICIA CIVIL ... 610820.43 7801707.67 SIM NÃO NÃO INFORMADO 2016 1 -43.941446 -19.877669 Parque São João Batista
2016-01-01 01:00:00 2016-001865528-001 H06002 ATROPELAMENTO DE PESSOA SEM VITIMA FATAL ... 0 NAO INFORMADO 0 NAO INFORMADO 20 LESTE POLICIA CIVIL ... 616043.53 7799563.76 SIM NÃO NÃO INFORMADO 2016 1 -43.891427 -19.896736 Casa Branca
2016-01-01 01:30:00 2016-014489036-001 H01002 ABALROAMENTO COM VITIMA ... 0 NAO INFORMADO 0 NAO INFORMADO 23 NORTE POLICIA CIVIL ... 610686.91 7805465.91 SIM NÃO NÃO INFORMADO 2016 1 -43.942946 -19.843720 Padre Júlio Maria
2016-01-01 02:55:00 2016-000009920-001 H01002 ABALROAMENTO COM VITIMA ... 0 NAO INFORMADO 0 NAO INFORMADO 23 NORTE POLICIA MILITAR ... 610958.28 7806566.34 SIM NÃO NÃO INFORMADO 2016 1 -43.940421 -19.833761 Santa Isabel
2016-01-01 03:00:00 2016-000207284-001 H09002 COLISAO DE VEICULOS COM VITIMA ... 1 BOM 1 ASFALTO 19 CENTRO-SUL POLICIA CIVIL ... 611496.74 7793765.15 SIM NÃO NÃO INFORMADO 2016 1 -43.934506 -19.949393 Sion
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2022-12-31 18:08:00 2022-057273258-001 H01002 ABALROAMENTO COM VITIMA ... 1 BOM 1 ASFALTO 23 NORTE POLICIA MILITAR ... 612239.23 7810127.89 SIM NÃO NÃO INFORMADO 2022 12 -43.928406 -19.801509 Conjunto Zilah Spósito
2022-12-31 20:16:00 2022-057287120-001 H09002 COLISAO DE VEICULOS COM VITIMA ... 0 NAO INFORMADO 0 NAO INFORMADO 19 CENTRO-SUL POLICIA MILITAR ... 612986.62 7795656.61 SIM NÃO NÃO INFORMADO 2022 12 -43.920387 -19.932217 São Lucas
2022-12-31 20:29:00 2022-057285174-001 H09002 COLISAO DE VEICULOS COM VITIMA ... 0 NAO INFORMADO 0 NAO INFORMADO 20 LESTE POLICIA MILITAR ... 615392.56 7798300.78 SIM NÃO NÃO INFORMADO 2022 12 -43.897566 -19.908186 Belém
2022-12-31 20:50:00 2023-004511369-001 H09002 COLISAO DE VEICULOS COM VITIMA ... 0 NAO INFORMADO 0 NAO INFORMADO 25 PAMPULHA POLICIA MILITAR ... 606434.82 7800642.99 SIM NÃO NÃO INFORMADO 2022 12 -43.983271 -19.887533 Engenho Nogueira
2022-12-31 21:52:00 2022-057298812-001 H08002 CHOQUE MECANICO COM VITIMA ... 2 CHUVA 1 ASFALTO 20 LESTE POLICIA MILITAR ... 612202.25 7796880.84 SIM NÃO NÃO INFORMADO 2022 12 -43.927955 -19.921201 Floresta

75619 rows × 22 columns

2. Faça um gráfico de linhas por ano mês indicando o número de acidentes naquele ano mês.¶

In [5]:
grouped_by_month = df.groupby(['ano', 'mes']).size()
grouped_by_month.plot(kind='line')
plt.ylabel('Número de Acidentes')
Out[5]:
Text(0, 0.5, 'Número de Acidentes')
No description has been provided for this image

3. Repita o gráfico acima por ano apenas.¶

In [6]:
grouped_by_year = df.groupby('ano').size()
grouped_by_year.plot(kind='line')
plt.ylabel('Número de Acidentes')
Out[6]:
Text(0, 0.5, 'Número de Acidentes')
No description has been provided for this image

4. Faça um gráfico de barras por ano indicandos os tipos de acidentes mais comuns no ano.¶

In [7]:
import random
import random

years = df.get('ano').unique().tolist()

colors = ['#D354F8', '#595B8E', '#31A8A6', '#F54C50', '#14C50C', '#14B1A6', '#183EAA']

for i in range(len(colors)):
    while i > 0 and colors[i] == colors[i-1]:
        colors[i] = '#' + ''.join(random.choices('0123456789ABCDEF', k=6))

spacing = 1
bar_width = 0.1
num_accident_kinds = df.groupby('tipo_acidente').size()

plt.figure(figsize=(12, 8))
for i, year in enumerate(years):
    accidents_per_year = df[df.get('ano') == year].groupby('tipo_acidente').size()
    ax = accidents_per_year.plot(kind='bar', color=colors[i], position=i * (bar_width + spacing), width=bar_width, label=year, edgecolor='black')

plt.title('Tipo de Acidente mais comum por Ano')
plt.xlabel('Tipo de Acidente')
plt.ylabel('Número de Acidentes')
plt.legend()

plt.show()
No description has been provided for this image

5. Repita o gráfico acima considerando gráficos fatais e não fatais.¶

In [8]:
fatals = df[df.get('indicador_fatalidade') == 'SIM'].groupby('tipo_acidente').size()
non_fatals = df[df.get('indicador_fatalidade') == 'NÃO'].groupby('tipo_acidente').size()

ax = fatals.plot(kind='bar', color='red', position=0, width=0.4, label='Fatais')
non_fatals.plot(kind='bar', color='blue', position=1, width=0.4, ax=ax, label='Não Fatais')

plt.title('Acidentes de Trânsito por Tipo e Gravidade')
plt.xlabel('Tipo de Acidente')
plt.ylabel('Número de Acidentes')
plt.legend()

plt.show()
No description has been provided for this image

6. Faça a mesma análise por bairro e por acidentes fatais e não fatais¶

In [9]:
grouped_by_year
neighborhood_records = {}
for neighborhood in df.get('bairro').unique():
    records = df[df.get('bairro') == neighborhood]
    neighborhood_records[neighborhood] = records
neighborhood_records_fatals = {neighborhood: neighborhood_records[neighborhood][neighborhood_records[neighborhood].get('indicador_fatalidade') == 'SIM'].groupby('ano') for neighborhood in neighborhood_records}
neighborhood_records_non_fatals = {neighborhood: neighborhood_records[neighborhood][neighborhood_records[neighborhood].get('indicador_fatalidade') == 'NÃO'].groupby('ano') for neighborhood in neighborhood_records}
In [10]:
bar_width = 0.3
spacing = 0.3

for i, year in enumerate(years):
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 24))

    df_year = df[df.get('ano') == year]
    
    fatals = df_year[df_year.get('indicador_fatalidade') == 'SIM'].groupby('bairro').size()
    non_fatals = df_year[df_year.get('indicador_fatalidade') == 'NÃO'].groupby('bairro').size()
    
    fatals.plot(kind='barh', color=colors[i], position=i * (bar_width + spacing), width=bar_width, ax=ax1, label=f'{year} Fatais', edgecolor='black')
    
    non_fatals.plot(kind='barh', color=colors[i], position=i * (bar_width + spacing), width=bar_width, ax=ax2, label=f'{year} Não Fatais', edgecolor='black')

    ax1.set_title(f'Acidentes Fatais por Bairro em {year}')
    ax1.set_ylabel('Bairro')
    ax1.set_xlabel('Número de Acidentes')
    ax1.legend()

    ax2.set_title(f'Acidentes Não Fatais por Bairro em {year}')
    ax2.set_ylabel('Bairro')
    ax2.set_xlabel('Número de Acidentes')
    ax2.legend()

    plt.tight_layout()

    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

7. Plotar Mapa de Belo Horizonte por Tipo de Acidente:¶

  • Faça um gráfico de dispersão das latitudes e longitudes. O mesmo deve parecer com o mapa de BH.
    Para entender bem os tipos de acidentes, faça gráficos por tipos diferentes de acidentes.
In [11]:
df.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Mapa de Belo Horizonte')
Out[11]:
Text(0.5, 1.0, 'Mapa de Belo Horizonte')
No description has been provided for this image

Os tipos de acidentes são :

In [12]:
df.get("desc_tipo_acidente").unique()
Out[12]:
array(['QUEDA DE PESSOA DE VEICULO                        ',
       'ATROPELAMENTO DE PESSOA SEM VITIMA FATAL          ',
       'ABALROAMENTO COM VITIMA                           ',
       'COLISAO DE VEICULOS COM VITIMA                    ',
       'CHOQUE MECANICO COM VITIMA                        ',
       'CAPOTAMENTO/TOMBAMENTO COM VITIMA                 ',
       'OUTROS COM VITIMA                                 ',
       'QUEDA E/OU VAZAMENTO DE CARGA DE VEICULO C/ VITIMA',
       'ATROPELAMENTO DE PESSOA COM VITIMA FATAL          ',
       'QUEDA DE VEICULO COM VITIMA                       ',
       'ATROPELAMENTO DE ANIMAL COM VITIMA                ',
       'CAPOTAMENTO/TOMBAMENTO SEM VITIMA                 '], dtype=object)

Acidentes de queda de pessoa por veículo por latitude e longitude :

In [13]:
grouped_by_QUEDA_DE_PESSOA_DE_VEICULO = df[(df.get('desc_tipo_acidente') == 'QUEDA DE PESSOA DE VEICULO                        ')]
grouped_by_QUEDA_DE_PESSOA_DE_VEICULO.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Acidentes de queda de pessoa por veículo por latitude e longitude')
Out[13]:
Text(0.5, 1.0, 'Acidentes de queda de pessoa por veículo por latitude e longitude')
No description has been provided for this image

Acidentes de atropelamento de pessoa sem vítima fatal por latitude e longitude :

In [14]:
grouped_by_ATROPELAMENTO_DE_PESSOA_SEM_VITIMA_FATAL = df[(df.get('desc_tipo_acidente') == 'ATROPELAMENTO DE PESSOA SEM VITIMA FATAL          ')]
grouped_by_ATROPELAMENTO_DE_PESSOA_SEM_VITIMA_FATAL.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Acidentes de atropelamento de pessoa sem vítima fatal por latitude e longitude')
Out[14]:
Text(0.5, 1.0, 'Acidentes de atropelamento de pessoa sem vítima fatal por latitude e longitude')
No description has been provided for this image

Acidentes de abalroamento com vítima por latitude e longitude :

In [15]:
grouped_by_ABALROAMENTO_COM_VITIMA = df[(df.get('desc_tipo_acidente') == 'ABALROAMENTO COM VITIMA                           ')]
grouped_by_ABALROAMENTO_COM_VITIMA.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Acidentes de abalroamento com vítima por latitude e longitude')
Out[15]:
Text(0.5, 1.0, 'Acidentes de abalroamento com vítima por latitude e longitude')
No description has been provided for this image

Acidentes de colisão de veículos com vítima por latitude e longitude :

In [16]:
grouped_by_COLISAO_DE_VEICULOS_COM_VITIMA= df[(df.get('desc_tipo_acidente') == 'COLISAO DE VEICULOS COM VITIMA                    ')]
grouped_by_COLISAO_DE_VEICULOS_COM_VITIMA.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Acidentes de colisão de veículos com vítima por latitude e longitude')
Out[16]:
Text(0.5, 1.0, 'Acidentes de colisão de veículos com vítima por latitude e longitude')
No description has been provided for this image

Acidentes de choque mecânico com vítima por latitude e longitude :

In [17]:
grouped_by_CHOQUE_MECANICO_COM_VITIMA= df[(df.get('desc_tipo_acidente') == 'CHOQUE MECANICO COM VITIMA                        ')]
grouped_by_CHOQUE_MECANICO_COM_VITIMA.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Acidentes de choque mecânico com vítima por latitude e longitude')
Out[17]:
Text(0.5, 1.0, 'Acidentes de choque mecânico com vítima por latitude e longitude')
No description has been provided for this image

Acidentes de capotamento ou tombamento com vítima por latitude e longitude :

In [18]:
grouped_by_CAPOTAMENTO_ou_TOMBAMENTO_COM_VITIMA= df[(df.get('desc_tipo_acidente') == 'CAPOTAMENTO/TOMBAMENTO COM VITIMA                 ')]
grouped_by_CAPOTAMENTO_ou_TOMBAMENTO_COM_VITIMA.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Acidentes de capotamento ou tombamento com vítima por latitude e longitude')
Out[18]:
Text(0.5, 1.0, 'Acidentes de capotamento ou tombamento com vítima por latitude e longitude')
No description has been provided for this image

Acidentes de outros com vítima por latitude e longitude :

In [19]:
grouped_by_OUTROS_COM_VITIMA= df[(df.get('desc_tipo_acidente') == 'OUTROS COM VITIMA                                 ')]
grouped_by_OUTROS_COM_VITIMA.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Acidentes de outros com vítima por latitude e longitude')
Out[19]:
Text(0.5, 1.0, 'Acidentes de outros com vítima por latitude e longitude')
No description has been provided for this image

Acidentes de queda e ou vazamento de carga de veículo com vítima por latitude e longitude :

In [20]:
grouped_by_QUEDA_E_OU_VAZAMENTO_DE_CARGA_DE_VEICULO_COM_VITIMA= df[(df.get('desc_tipo_acidente') == 'QUEDA E/OU VAZAMENTO DE CARGA DE VEICULO C/ VITIMA')]
grouped_by_QUEDA_E_OU_VAZAMENTO_DE_CARGA_DE_VEICULO_COM_VITIMA.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Acidentes de queda e ou vazamento de carga de veículo com vítima por latitude e longitude')
Out[20]:
Text(0.5, 1.0, 'Acidentes de queda e ou vazamento de carga de veículo com vítima por latitude e longitude')
No description has been provided for this image

Acidentes de atropelamento de pessoa com vítima fatal por latitude e longitude :

In [21]:
grouped_by_ATROPELAMENTO_DE_PESSOA_COM_VITIMA_FATAL= df[(df.get('desc_tipo_acidente') == 'ATROPELAMENTO DE PESSOA COM VITIMA FATAL          ')]
grouped_by_ATROPELAMENTO_DE_PESSOA_COM_VITIMA_FATAL.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Acidentes de atropelamento de pessoa com vítima fatal por latitude e longitude')
Out[21]:
Text(0.5, 1.0, 'Acidentes de atropelamento de pessoa com vítima fatal por latitude e longitude')
No description has been provided for this image

Acidentes de queda de veículo com vítima por latitude e longitude :

In [22]:
grouped_by_QUEDA_DE_VEICULO_COM_VITIMA= df[(df.get('desc_tipo_acidente') == 'QUEDA DE VEICULO COM VITIMA                       ')]
grouped_by_QUEDA_DE_VEICULO_COM_VITIMA.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Acidentes de queda de veículo com vítima por latitude e longitude')
Out[22]:
Text(0.5, 1.0, 'Acidentes de queda de veículo com vítima por latitude e longitude')
No description has been provided for this image

Acidentes de atropelamento de animal com vítima por latitude e longitude :

In [23]:
grouped_by_ATROPELAMENTO_DE_ANIMAL_COM_VITIMA= df[(df.get('desc_tipo_acidente') == 'ATROPELAMENTO DE ANIMAL COM VITIMA                ')]
grouped_by_ATROPELAMENTO_DE_ANIMAL_COM_VITIMA.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Acidentes de atropelamento de animal com vítima por latitude e longitude')
Out[23]:
Text(0.5, 1.0, 'Acidentes de atropelamento de animal com vítima por latitude e longitude')
No description has been provided for this image

Acidentes de capotamento ou tombamento sem vítima por latitude e longitude :

In [24]:
grouped_by_CAPOTAMENTO_ou_TOMBAMENTO_SEM_VITIMA= df[(df.get('desc_tipo_acidente') == 'CAPOTAMENTO/TOMBAMENTO SEM VITIMA                 ')]
grouped_by_CAPOTAMENTO_ou_TOMBAMENTO_SEM_VITIMA.plot(
  kind='hexbin',
  x='lat',
  y='lon',
  mincnt=1,
  gridsize=75,
  bins='log'
 )
plt.title('Acidentes de capotamento ou tombamento sem vítima por latitude e longitude')
Out[24]:
Text(0.5, 1.0, 'Acidentes de capotamento ou tombamento sem vítima por latitude e longitude')
No description has been provided for this image

8. Plotar Intervalo de Confiança via Bootstrap do Número de Acidentes por Mês:¶

  • Utilize a técnica de bootstrap para calcular intervalos de confiança para o número de acidentes em cada mês. Plote esses intervalos juntamente com a média mensal dos acidentes para visualizar a variabilidade e a confiança das estimativas.

Criando o Data Frame com o número de acidentes em cada mês:

In [25]:
acidentes_por_mes = df.groupby(['ano', 'mes']).count()
acidentes_por_mes = acidentes_por_mes.assign(num_acidentes = acidentes_por_mes.get("bairro")).reset_index()
acidentes_por_mes = acidentes_por_mes.drop(columns = ["numero_boletim", "tipo_acidente", "desc_tipo_acidente", "cod_tempo", "desc_tempo", "cod_pavimento", "pavimento", "cod_regional", "desc_regional", "origem_boletim", "velocidade_permitida", "coordenada_x", "coordenada_y", "hora_informada", "indicador_fatalidade", "descricao_ups", "local_sinalizado", "lat", "lon", "bairro"])
acidentes_por_mes
Out[25]:
ano mes num_acidentes
0 2016 1 847
1 2016 2 838
2 2016 3 964
3 2016 4 948
4 2016 5 972
... ... ... ...
79 2022 8 975
80 2022 9 1001
81 2022 10 1097
82 2022 11 983
83 2022 12 990

84 rows × 3 columns

Realizando o bootstrap :

In [26]:
media_acidentes_por_mes= np.array([])
for i in np.arange(10000):
    media = acidentes_por_mes.sample(84, replace = True).get("num_acidentes").mean()
    media_acidentes_por_mes = np.append(media_acidentes_por_mes, media)
media_acidentes_por_mes 
Out[26]:
array([888.02380952, 887.01190476, 903.07142857, ..., 907.63095238,
       894.9047619 , 900.0952381 ])

Encontrando o intervalo de confiança de 95% :

In [27]:
right = np.percentile(media_acidentes_por_mes, 97.5)
left = np.percentile(media_acidentes_por_mes, 2.5)
[left,right]
Out[27]:
[878.7017857142857, 920.6431547619047]
In [28]:
bpd.DataFrame().assign( Media_bootstrap=media_acidentes_por_mes).plot(kind='hist', density=True, ec='w', figsize=(10, 5))
plt.plot([left, right], [0, 0], color='gold', linewidth=12, label='Intevalo de confiança 95%')
plt.legend()
Out[28]:
<matplotlib.legend.Legend at 0x25a956c97c0>
No description has been provided for this image

9. Vamos brincar de regressão.¶

  • Inicialmente, faça um gráfico de dispersão onde o eixo X é o número de acidentes em 2019, 2020 ou 2021.
  • Serão três gráficos.
  • Cada ponto no gráfico vai corresponder a um bairro.

Criando todos os mergs :

In [29]:
cont = df.groupby(['bairro', 'ano']).size().reset_index()
dados_2019 = cont[cont.get('ano') == 2019]
dados_2020 = cont[cont.get('ano') == 2020]
dados_2021 = cont[cont.get('ano') == 2021]
merge_by_acidentes_2019_e_2020 = dados_2019.merge(
  dados_2020,
  on='bairro')
merge_by_acidentes_2019_e_2021 = dados_2019.merge(
  dados_2021,
  on='bairro')
merge_by_acidentes_2020_e_2021 = dados_2020.merge(
  dados_2021,
  on='bairro')
In [30]:
merge_by_acidentes_2019_e_2020
Out[30]:
bairro ano_x 0_x ano_y 0_y
0 Aarão Reis 2019 47 2020 60
1 Acaiaca 2019 11 2020 10
2 Adelaide 2019 39 2020 25
3 Alto dos Pinheiros 2019 19 2020 16
4 Alípio de Melo 2019 35 2020 24
... ... ... ... ... ...
288 Vitória 2019 19 2020 25
289 Vitória da Conquista 2019 7 2020 6
290 Washington Pires 2019 5 2020 9
291 Álvaro Camargos 2019 10 2020 12
292 Átila de Paiva 2019 77 2020 65

293 rows × 5 columns

In [31]:
merge_by_acidentes_2019_e_2021
Out[31]:
bairro ano_x 0_x ano_y 0_y
0 Aarão Reis 2019 47 2021 66
1 Acaiaca 2019 11 2021 16
2 Adelaide 2019 39 2021 35
3 Alto dos Pinheiros 2019 19 2021 26
4 Alípio de Melo 2019 35 2021 39
... ... ... ... ... ...
288 Vitória 2019 19 2021 16
289 Vitória da Conquista 2019 7 2021 11
290 Washington Pires 2019 5 2021 5
291 Álvaro Camargos 2019 10 2021 6
292 Átila de Paiva 2019 77 2021 51

293 rows × 5 columns

In [32]:
merge_by_acidentes_2020_e_2021
Out[32]:
bairro ano_x 0_x ano_y 0_y
0 Aarão Reis 2020 60 2021 66
1 Acaiaca 2020 10 2021 16
2 Adelaide 2020 25 2021 35
3 Alto dos Pinheiros 2020 16 2021 26
4 Alípio de Melo 2020 24 2021 39
... ... ... ... ... ...
287 Vitória 2020 25 2021 16
288 Vitória da Conquista 2020 6 2021 11
289 Washington Pires 2020 9 2021 5
290 Álvaro Camargos 2020 12 2021 6
291 Átila de Paiva 2020 65 2021 51

292 rows × 5 columns

In [33]:
merge_by_acidentes_2019_e_2020.plot(kind = "scatter", x = "0_x", y = "0_y" )
plt.xlabel('Número de acidentes por bairro em 2019')
plt.ylabel('Número de acidentes por bairro em 2020')
Out[33]:
Text(0, 0.5, 'Número de acidentes por bairro em 2020')
No description has been provided for this image
In [34]:
merge_by_acidentes_2019_e_2021.plot(kind = "scatter", x = "0_x", y = "0_y" )
plt.xlabel('Número de acidentes por bairro em 2019')
plt.ylabel('Número de acidentes por bairro em 2021')
Out[34]:
Text(0, 0.5, 'Número de acidentes por bairro em 2021')
No description has been provided for this image
In [35]:
merge_by_acidentes_2020_e_2021.plot(kind = "scatter", x = "0_x", y = "0_y" )
plt.xlabel('Número de acidentes por bairro em 2020')
plt.ylabel('Número de acidentes por bairro em 2021')
Out[35]:
Text(0, 0.5, 'Número de acidentes por bairro em 2021')
No description has been provided for this image

10. Use o Número de Acidentes por bairro de 2019 para Prever 2022:¶

  • Também use os de 2020
  • Por fim use os de 2022
  • Aplique métodos de previsão (como modelos de regressão) para estimar o número de acidentes em 2023, utilizando os dados passados
  • Avalie os erros do modelo e discuta as limitações
  • Compare os três modelos. A pandemia teve algum impacto, qual?

Primeiramente, vamos definir uma função que expressa X ou Y (colunas de um DataFrame) em unidades padronizadas :

In [36]:
def standard_units(col):
    return (col - col.mean()) / np.std(col)

Agora uma função que calcula a correlação R a partir das unidades padronizadas:

In [37]:
def calculate_r(df, x, y):
    '''Returns the average value of the product of x and y, 
       when both are measured in standard units.'''
    x_su = standard_units(df.get(x))
    y_su = standard_units(df.get(y))
    return (x_su * y_su).mean()

Criando o merge para prever o número de acidentes por bairro de 2022 usando 2019 :

In [38]:
dados_2022 = cont[cont.get('ano') == 2022]
merge_by_acidentes_2019_e_2022 = dados_2019.merge(
  dados_2022,
  on='bairro')
merge_by_acidentes_2019_e_2022
Out[38]:
bairro ano_x 0_x ano_y 0_y
0 Aarão Reis 2019 47 2022 63
1 Acaiaca 2019 11 2022 12
2 Adelaide 2019 39 2022 31
3 Alto dos Pinheiros 2019 19 2022 24
4 Alípio de Melo 2019 35 2022 31
... ... ... ... ... ...
286 Vitória 2019 19 2022 19
287 Vitória da Conquista 2019 7 2022 6
288 Washington Pires 2019 5 2022 8
289 Álvaro Camargos 2019 10 2022 12
290 Átila de Paiva 2019 77 2022 78

291 rows × 5 columns

Funções para calcular o a e o b no caso geral :

In [39]:
def slope(df, x, y):
    "Returns the slope of the regression line between columns x and y in df (in original units)."
    r = calculate_r(df, x, y)
    return r * np.std(df.get(y)) / np.std(df.get(x))

def intercept(df, x, y):
    "Returns the intercept of the regression line between columns x and y in df (in original units)."
    return df.get(y).mean() - slope(df, x, y) * df.get(x).mean()
In [40]:
slope_2019_2022 = slope(merge_by_acidentes_2019_e_2022, "0_x", "0_y")
intercept_2019_2022 = intercept(merge_by_acidentes_2019_e_2022, "0_x", "0_y")
In [41]:
predict_2022_usando_2019 = merge_by_acidentes_2019_e_2022.assign(num_2022_previsto = slope_2019_2022 * merge_by_acidentes_2019_e_2022.get("0_y") + intercept_2019_2022)
predict_2022_usando_2019
Out[41]:
bairro ano_x 0_x ano_y 0_y num_2022_previsto
0 Aarão Reis 2019 47 2022 63 57.561025
1 Acaiaca 2019 11 2022 12 13.713688
2 Adelaide 2019 39 2022 31 30.048970
3 Alto dos Pinheiros 2019 19 2022 24 24.030708
4 Alípio de Melo 2019 35 2022 31 30.048970
... ... ... ... ... ... ...
286 Vitória 2019 19 2022 19 19.731950
287 Vitória da Conquista 2019 7 2022 6 8.555177
288 Washington Pires 2019 5 2022 8 10.274681
289 Álvaro Camargos 2019 10 2022 12 13.713688
290 Átila de Paiva 2019 77 2022 78 70.457301

291 rows × 6 columns

In [42]:
comparacao_previsto_real_2022_usando_2019 = predict_2022_usando_2019.assign(residual= (predict_2022_usando_2019.get("0_y") - predict_2022_usando_2019.get("num_2022_previsto")))
comparacao_previsto_real_2022_usando_2019
Out[42]:
bairro ano_x 0_x ano_y 0_y num_2022_previsto residual
0 Aarão Reis 2019 47 2022 63 57.561025 5.438975
1 Acaiaca 2019 11 2022 12 13.713688 -1.713688
2 Adelaide 2019 39 2022 31 30.048970 0.951030
3 Alto dos Pinheiros 2019 19 2022 24 24.030708 -0.030708
4 Alípio de Melo 2019 35 2022 31 30.048970 0.951030
... ... ... ... ... ... ... ...
286 Vitória 2019 19 2022 19 19.731950 -0.731950
287 Vitória da Conquista 2019 7 2022 6 8.555177 -2.555177
288 Washington Pires 2019 5 2022 8 10.274681 -2.274681
289 Álvaro Camargos 2019 10 2022 12 13.713688 -1.713688
290 Átila de Paiva 2019 77 2022 78 70.457301 7.542699

291 rows × 7 columns

Agora, usando 2020:

In [43]:
merge_by_acidentes_2020_e_2022 = dados_2020.merge(
  dados_2022,
  on='bairro')
merge_by_acidentes_2020_e_2022
Out[43]:
bairro ano_x 0_x ano_y 0_y
0 Aarão Reis 2020 60 2022 63
1 Acaiaca 2020 10 2022 12
2 Adelaide 2020 25 2022 31
3 Alto dos Pinheiros 2020 16 2022 24
4 Alípio de Melo 2020 24 2022 31
... ... ... ... ... ...
286 Vitória 2020 25 2022 19
287 Vitória da Conquista 2020 6 2022 6
288 Washington Pires 2020 9 2022 8
289 Álvaro Camargos 2020 12 2022 12
290 Átila de Paiva 2020 65 2022 78

291 rows × 5 columns

In [44]:
slope_2020_2022 = slope(merge_by_acidentes_2020_e_2022, "0_x", "0_y")
intercept_2020_2022 = intercept(merge_by_acidentes_2020_e_2022, "0_x", "0_y")
In [45]:
predict_2022_usando_2020 = merge_by_acidentes_2020_e_2022.assign(num_2022_previsto = slope_2020_2022 * merge_by_acidentes_2020_e_2022.get("0_y") + intercept_2020_2022)
predict_2022_usando_2020
Out[45]:
bairro ano_x 0_x ano_y 0_y num_2022_previsto
0 Aarão Reis 2020 60 2022 63 73.473589
1 Acaiaca 2020 10 2022 12 13.684818
2 Adelaide 2020 25 2022 31 35.959066
3 Alto dos Pinheiros 2020 16 2022 24 27.752764
4 Alípio de Melo 2020 24 2022 31 35.959066
... ... ... ... ... ... ...
286 Vitória 2020 25 2022 19 21.891120
287 Vitória da Conquista 2020 6 2022 6 6.650845
288 Washington Pires 2020 9 2022 8 8.995503
289 Álvaro Camargos 2020 12 2022 12 13.684818
290 Átila de Paiva 2020 65 2022 78 91.058521

291 rows × 6 columns

In [46]:
comparacao_previsto_real_2022_usando_2020 = predict_2022_usando_2020.assign(residual= (predict_2022_usando_2020.get("0_y") - predict_2022_usando_2020.get("num_2022_previsto")))
comparacao_previsto_real_2022_usando_2020
Out[46]:
bairro ano_x 0_x ano_y 0_y num_2022_previsto residual
0 Aarão Reis 2020 60 2022 63 73.473589 -10.473589
1 Acaiaca 2020 10 2022 12 13.684818 -1.684818
2 Adelaide 2020 25 2022 31 35.959066 -4.959066
3 Alto dos Pinheiros 2020 16 2022 24 27.752764 -3.752764
4 Alípio de Melo 2020 24 2022 31 35.959066 -4.959066
... ... ... ... ... ... ... ...
286 Vitória 2020 25 2022 19 21.891120 -2.891120
287 Vitória da Conquista 2020 6 2022 6 6.650845 -0.650845
288 Washington Pires 2020 9 2022 8 8.995503 -0.995503
289 Álvaro Camargos 2020 12 2022 12 13.684818 -1.684818
290 Átila de Paiva 2020 65 2022 78 91.058521 -13.058521

291 rows × 7 columns

Comparando o erro de previsão dos dois modelos :

In [47]:
residual_total_usando_2019 = ((comparacao_previsto_real_2022_usando_2019.get("residual") ** 2).sum()) / 291
residual_total_usando_2019
Out[47]:
39.17828645346526
In [48]:
residual_total_usando_2020 = ((comparacao_previsto_real_2022_usando_2020.get("residual") ** 2).sum()) / 291
residual_total_usando_2020
Out[48]:
92.17862044575266

Assim, concluimos que usar o número de acidentes por bairro de 2019 para prever os 2022 é melhor do que usar os número de acidentes por bairro de 2020.

Agora vamos prever os números de acidentes por bairro de 2023 :

In [49]:
def linear_regression(x, y):
    x_mean = np.mean(x)
    y_mean = np.mean(y)
    beta_1 = np.sum((x - x_mean) * (y - y_mean)) / np.sum((x - x_mean) ** 2)
    beta_0 = y_mean - beta_1 * x_mean
    return beta_0, beta_1
In [50]:
grouped_by_bairro_and_year = df.groupby(['bairro', 'ano']).size().reset_index()
grouped_by_bairro_and_year

years = grouped_by_bairro_and_year.get('ano').unique()
predictions_2023 = {}

for neighborhood in grouped_by_bairro_and_year.get('bairro').unique():
    accidents = grouped_by_bairro_and_year[grouped_by_bairro_and_year.get('bairro') == neighborhood].get([0]).values
    beta_0, beta_1 = linear_regression(years, accidents)
    prediction = beta_0 + beta_1 * 2023
    predictions_2023[neighborhood] = prediction

pred_2023_df = bpd.DataFrame().assign(bairro=list(predictions_2023.keys()), num_acidentes=list(predictions_2023.values()))
pred_2023_df
Out[50]:
bairro num_acidentes
0 Aarão Reis 61.000000
1 Acaiaca 14.857143
2 Adelaide 32.571429
3 Alto dos Pinheiros 20.857143
4 Alípio de Melo 32.428571
... ... ...
291 Vitória 17.428571
292 Vitória da Conquista 7.714286
293 Washington Pires 7.285714
294 Álvaro Camargos 7.428571
295 Átila de Paiva 66.571429

296 rows × 2 columns

In [51]:
merge_by_acidentes_2019_e_2023 = dados_2019.merge(
    pred_2023_df,
    on='bairro'
)

merge_by_acidentes_2020_e_2023 = dados_2020.merge(
    pred_2023_df,
    on='bairro'
)

merge_by_acidentes_2021_e_2023 = dados_2021.merge(
    pred_2023_df,
    on='bairro'
)

merge_by_acidentes_2022_e_2023 = dados_2022.merge(
    pred_2023_df,
    on='bairro'
)

merge_by_acidentes_2019_e_2023.plot(kind = "scatter", x = merge_by_acidentes_2019_e_2023.columns[2], y = "num_acidentes" )
plt.xlabel('Número de acidentes por bairro em 2019')
plt.ylabel('Número de acidentes por bairro em 2023')

merge_by_acidentes_2020_e_2023.plot(kind = "scatter", x = merge_by_acidentes_2020_e_2023.columns[2], y = "num_acidentes" )
plt.xlabel('Número de acidentes por bairro em 2020')
plt.ylabel('Número de acidentes por bairro em 2023')

merge_by_acidentes_2021_e_2023.plot(kind = "scatter", x = merge_by_acidentes_2021_e_2023.columns[2], y = "num_acidentes" )
plt.xlabel('Número de acidentes por bairro em 2021')
plt.ylabel('Número de acidentes por bairro em 2023')

merge_by_acidentes_2022_e_2023.plot(kind = "scatter", x = merge_by_acidentes_2022_e_2023.columns[2], y = "num_acidentes" )
plt.xlabel('Número de acidentes por bairro em 2022')
plt.ylabel('Número de acidentes por bairro em 2023')
plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Ao observarmos os modelos de regressão e os gráficos gerados para:

  • 2019 -> 2023
  • 2020 -> 2023
  • 2021 -> 2023
  • 2022 -> 2023

Podemos observar que a linha de regressão, nos anos da pandemia, se eleva mais do quem em 2019 e 2022, fato que pode ser explicado pelo isolamento social, o qual acarretou o esvaziamento das vias públicas, resultando em menos acidentes.

Portanto, chegamos a conclusão de que pandemia teve impactos indiscutíveis nos padrões de trânsito, uma vez que era recomendado a todas as pessoas evitar ao máximo sair de suas casas.
Deste modo, utilizar de linhas de regressão pode acabar sendo um método limitado para fazer previsões em situações referentes a momentos antes/durante pandemia, tendo em vista que os impactos do lockdown fogem do padrão de acontecimentos normais.

11. Fazer Análises Adicionais de Interesse:¶

  • Realize análises adicionais que sejam de seu interesse ou relevância para o projeto. Isso pode incluir a correlação entre diferentes variáveis, a análise de hotspots de acidentes, ou a investigação de fatores contribuintes para a gravidade dos acidentes.

Vamos analisar os hotspost de acidentes por região :

In [52]:
grouped_by_regiao = df.groupby("desc_regional").size().sort_values(ascending = False)
grouped_by_regiao
Out[52]:
desc_regional
CENTRO-SUL         12900
PAMPULHA           10188
NOROESTE            9014
OESTE               8764
NORDESTE            7739
VENDA NOVA          6583
BARREIRO            6479
LESTE               6220
NORTE               5536
                    2196
dtype: int64

Assim concluimos que a região Centro-Sul é a regiao com mais acidentes.

  • Agora vamos analisar qual é a região com mais acidentes por quilômetro quadrado, fazendo uma rápida pesquisa as áreas são : Centro-Sul = 31,53 Pampulha = 47,13 Noroeste = 38,16 Oeste = 33,39 Nordeste = 39,59 Venda Nova = 27,80 Barreiro = 53,51 Leste = 28,52 Norte = 33,21
In [53]:
acidentes__por_km_centro_sul = 409,1
acidentes__por_km_pampulha = 216,1
acidentes__por_km_noroeste = 236,2
acidentes__por_km_oeste  = 262,4
acidentes__por_km_nordeste = 195,4
acidentes__por_km_venda_nova = 236,7
acidentes__por_km_barreiro = 121,0
acidentes__por_km_leste = 218,0
acidentes__por_km_norte = 166,6

A região Centro-Sul continua sendo a região com mais acidentes por quilômetro quadrado.

Agora vamos fazer a mesma analise por bairro:

In [54]:
grouped_by_bairro = df.groupby("bairro").size().sort_values(ascending = False)
grouped_by_bairro
Out[54]:
bairro
Jardim Montanhês                        2770
Centro                                  2566
Funcionários                            1387
Barreiro de Cima                        1033
Lourdes                                  962
                                        ... 
Conjunto Habitacional Vale do Jatobá      13
Vila das Antenas                           9
Nova Pampulha                              7
Mangueiras (Vale do Jatobá)                5
Novo Santa Cecília (Vale do Jatobá)        3
Length: 296, dtype: int64

Observamos uma grande concentração de acidentes nos bairros, Jardim Montanhês e Centro, caracterizando os moairoes hotspots de acidentes em Belo Horizonte.

Vamos analisar agora os acidentes fatais :

In [55]:
acidentes_fatais = df[df.get("indicador_fatalidade") == "SIM"]
acidentes_fatais
Out[55]:
numero_boletim tipo_acidente desc_tipo_acidente cod_tempo desc_tempo cod_pavimento pavimento cod_regional desc_regional origem_boletim ... coordenada_x coordenada_y hora_informada indicador_fatalidade descricao_ups ano mes lat lon bairro
data_boletim
2016-01-07 15:54:00 2016-014209895-001 H06001 ATROPELAMENTO DE PESSOA COM VITIMA FATAL ... 1 BOM 1 ASFALTO 24 OESTE POLICIA MILITAR ... 604394.37 7794721.04 SIM SIM NÃO INFORMADO 2016 7 -44.002425 -19.941150 Jardinópolis
2016-01-07 19:03:00 2016-014224486-001 H06001 ATROPELAMENTO DE PESSOA COM VITIMA FATAL ... 1 BOM 2 CONCRETO 22 NOROESTE POLICIA MILITAR ... 606204.96 7796701.73 SIM SIM NÃO INFORMADO 2016 7 -43.985240 -19.923156 Coração Eucarístico
2016-01-12 16:48:00 2016-026065911-001 H01002 ABALROAMENTO COM VITIMA ... 2 CHUVA 1 ASFALTO 26 VENDA NOVA POLICIA MILITAR ... 608932.89 7808965.54 SIM SIM NÃO INFORMADO 2016 12 -43.959901 -19.812198 Venda Nova
2016-01-14 22:20:00 2016-000710249-001 H99002 OUTROS COM VITIMA ... 2 CHUVA 1 ASFALTO 23 NORTE POLICIA MILITAR ... 609829.67 7807202.61 SIM SIM NÃO INFORMADO 2016 1 -43.951235 -19.828076 Campo Alegre
2016-01-15 23:37:00 2016-001124317-001 H08002 CHOQUE MECANICO COM VITIMA ... 3 NEBLINA 1 ASFALTO 19 CENTRO-SUL POLICIA MILITAR ... 614010.08 7792909.97 SIM SIM NÃO INFORMADO 2016 1 -43.910438 -19.956973 Mangabeiras
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2022-11-24 18:00:00 2022-051500903-001 H99002 OUTROS COM VITIMA ... 1 BOM 1 ASFALTO 19 CENTRO-SUL POLICIA MILITAR ... 610439.67 7797408.23 SIM SIM NÃO INFORMADO 2022 11 -43.944825 -19.916537 Centro
2022-11-27 01:50:00 2022-051851904-001 H08002 CHOQUE MECANICO COM VITIMA ... 1 BOM 1 ASFALTO 19 CENTRO-SUL POLICIA MILITAR ... 614267.03 7795308.48 SIM SIM NÃO INFORMADO 2022 11 -43.908132 -19.935288 Vila Santana do Cafezal
2022-12-03 03:00:00 2022-010761998-001 H09002 COLISAO DE VEICULOS COM VITIMA ... 1 BOM 1 ASFALTO 22 NOROESTE POLICIA MILITAR ... 609064.30 7799818.18 SIM SIM NÃO INFORMADO 2022 3 -43.958107 -19.894840 Senhor Bom Jesus
2022-12-05 14:19:00 2022-020254018-002 H06001 ATROPELAMENTO DE PESSOA COM VITIMA FATAL ... 1 BOM 1 ASFALTO 19 CENTRO-SUL POLICIA MILITAR ... 610763.74 7797350.16 SIM SIM NÃO INFORMADO 2022 5 -43.941726 -19.917043 Centro
2022-12-26 00:57:00 2022-056378379-001 H08002 CHOQUE MECANICO COM VITIMA ... 0 NAO INFORMADO 0 NAO INFORMADO 25 PAMPULHA POLICIA MILITAR ... 603444.20 7800384.96 SIM SIM NÃO INFORMADO 2022 12 -44.011822 -19.890025 São Sebastião (Ressaca)

669 rows × 22 columns

In [56]:
acidentes_fatais.groupby("desc_tempo").size()
Out[56]:
desc_tempo
BOM                445
CHUVA               66
NAO INFORMADO      140
NEBLINA              3
NUBLADO             15
dtype: int64
In [57]:
acidentes_fatais_com_tempo_ruim = 66 + 3 + 15
poropocao = acidentes_fatais_com_tempo_ruim / (669 - 140)
poropocao
Out[57]:
0.15879017013232513

Considerando apenas os acidentes com o tempo informado, a proporção de acientes fatais com um tempo ruim(chuva ou neblina ou nublado) é de aproximadamente 16%, uma quantidade bem significativa, mostrando a influência do clima nos acidentes de trânsito.